/*
 * Copyright (c) Zhongkehonglue Technologies Co., Ltd. 2025. All rights reserved.
 * Description: Implementation of functions related to the DMC DEV module.
 * Author: wangchunyang
 * Create: 2025-05-23
 */

#include "dmcEvent.h"
#include "dmcLog.h"
#include "dmcDevice.h"
#include "dmcCallback.h"

#ifdef LOG_TAG
#undef LOG_TAG
#endif

namespace DlnaDmc {

static dmc_int32 DmcHandleByebyeEvent(const void *pstEvent);
static dmc_int32 DmcHandleSearchresultEvent(const void *pstEvent);
static dmc_int32 IsDeviceOnline(dmc_uint32 deviceId, bool &onLine);
static void ScheduleJobForDevice(dmc_uint32 deviceId);
static void DmcDetectDeviceHeartBeat(void *para);
static std::mutex searchresultEventMtx;
static std::mutex byebyeEventMtx;

int DmcEventHandlerCb(Upnp_EventType eventType, const void *event, void *cookie)
{
    LOGI("event cb is called.");
    dmc_int32 iRet = DMC_SUCCESS;
    switch (eventType) {
        case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
        case UPNP_DISCOVERY_SEARCH_RESULT:
            iRet = DmcHandleSearchresultEvent(event);
            break;
        case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
            iRet = DmcHandleByebyeEvent(event);
            break;
        default:
            LOGI("UNHANDLED EVENT Type-:%{public}d ", eventType);
            break;
    }
    LOGI("DlnaDmcDevCallbackHandler ReturnVal:%{public}ld ", iRet);
    return iRet;
}

static dmc_int32 DmcHandleSearchresultEvent(const void *event)
{
    std::lock_guard<std::mutex> guard(searchresultEventMtx);
    dmc_int32 iRetVal;
    LOGI("Dmc Received Advertisement");
    if (event == nullptr) {
        LOGE("[DLNADMC][%{public}s][:%{public}d]  NULL Input Event Value");
        return DMC_FAILURE;
    }

    UpnpDiscovery* discovery = (UpnpDiscovery*)event;
    std::string udn = UpnpString_get_String(UpnpDiscovery_get_DeviceID(discovery));
    LOGI("new device, udn = %{public}s.", udn.c_str());
    if (DmcDevice::GetInstance().DoesUdnExist(udn)) {
        LOGI("udn = %{public}s, already exists.", udn.c_str());
        DmcDevice::GetInstance().UpdateLastUpdateTime(udn);
        return DMC_SUCCESS;
    }

    auto xmlDescInfoSharedp = DmcGetDescriptionInfo(event);
    if (xmlDescInfoSharedp == nullptr) {
        LOGE("dmc get desc info nullptr");
        return DMC_FAILURE;
    }

    auto ret = DmcDevice::GetInstance().AddDevice(xmlDescInfoSharedp);
    if (ret != DMC_SUCCESS) {
        return ret;
    }

    UploadDeviceInfo infoToUpload;
    infoToUpload.deviceId = xmlDescInfoSharedp->deviceId;
    infoToUpload.deviceType = DMC_DEVICE_TYPE_DMR;
    infoToUpload.friendlyName = xmlDescInfoSharedp->friendlyName;
    infoToUpload.baseURL = xmlDescInfoSharedp->baseUrl;
    infoToUpload.udn = xmlDescInfoSharedp->udn;
    infoToUpload.manufacturer = xmlDescInfoSharedp->manufacturer;
    infoToUpload.modelName = xmlDescInfoSharedp->modelName;

    DmcCallback::GetInstance().ExecDeviceUpCb(infoToUpload);
    return DMC_SUCCESS;
}


static dmc_int32 DmcHandleByebyeEvent(const void *event)
{
    std::lock_guard<std::mutex> guard(byebyeEventMtx);
    LOGE("bye bye event begin.");
    if (event == nullptr) {
        LOGE("event is nullptr.");
        return DMC_FAILURE;
    }
    UpnpDiscovery* discovery = (UpnpDiscovery*)event;
    std::string udn = UpnpString_get_String(UpnpDiscovery_get_DeviceID(discovery));
    LOGI("device byebye, udn = %{public}s.", udn.c_str());
    dmc_uint32 deviceId;
    auto ret = DmcDevice::GetInstance().GetDeviceIdByUdn(udn, deviceId);
    if (ret != DMC_SUCCESS) {
        LOGE("Get device id by udn failed.");
        return ret;
    }
    DmcCallback::GetInstance().ExecDeviceDownCb(deviceId);
    ret = DmcDevice::GetInstance().DeleteDevice(udn);
    if (ret != DMC_SUCCESS) {
        LOGE("delete device failed.");
        return ret;
    }
    LOGE("bye bye event end.");
    return DMC_SUCCESS;
}

static dmc_int32 IsDeviceOnline(dmc_uint32 deviceId, bool &onLine)
{
    std::string descUrl;
    auto ret = DmcDevice::GetInstance().GetDescUrl(deviceId, descUrl);
    if (ret != DMC_SUCCESS) {
        LOGE("Get desc url failed.");
        return ret;
    }
    IXML_Document* pDeviceDescripeDoc = nullptr;
    dmc_int32 iRetVal = UpnpDownloadXmlDoc(descUrl.c_str(), &pDeviceDescripeDoc);
    ixmlDocument_free(pDeviceDescripeDoc);
    if (UPNP_E_SUCCESS != iRetVal) {
        onLine = false;
        LOGE("Could not download device description, ret = %{public}ld ", iRetVal);
        return DMC_SUCCESS;
    }
    onLine = true;
    return DMC_SUCCESS;
}

static void DmcDetectDeviceHeartBeat(void *para)
{
    LOGI("HeartBeat begin.");
    if (para == nullptr) {
        LOGE("para nullptr.");
        return;
    }
    dmc_uint32 * p = static_cast<dmc_uint32 *>(para);
    dmc_uint32 deviceId = *p;
    delete p;
    p = nullptr;
    bool onLine;
    auto ret = IsDeviceOnline(deviceId, onLine);
    if (ret != DMC_SUCCESS) {
        LOGE("checking device heart beat failed.");
        return;
    }
    if (onLine) {
        DmcDevice::GetInstance().ResetHeartBeatCount(deviceId);
        LOGI("deviceId:%{public}u is online.", deviceId);
    } else {
        dmc_int32 failureCount;
        ret = DmcDevice::GetInstance().IncreaseHeartBeatCount(deviceId, failureCount);
        LOGE("deviceId:%{public}u is offline.", deviceId);
        if (ret == DMC_SUCCESS && failureCount > 1) {
            DmcDevice::GetInstance().DeleteByDeviceId(deviceId);
            DmcCallback::GetInstance().ExecDeviceDownCb(deviceId);
            LOGE("deviceId:%{public}u has been deleted.", deviceId);
            return;
        }
    }
    ScheduleJobForDevice(deviceId);
}

static void ScheduleJobForDevice(dmc_uint32 deviceId)
{
    LOGD("deviceId:%{public}u shedule job begin.", deviceId);
    ThreadPoolJob job;
    time_t time = 10;
    dmc_uint32* id = new dmc_uint32(deviceId);
    (void)TPJobInit(&job, DmcDetectDeviceHeartBeat, id);
    (void)TPJobSetFreeFunction(&job, nullptr);
    (void)TimerThreadSchedule(&gTimerThread, time, REL_SEC, &job, SHORT_TERM, nullptr);
}
}


